﻿#include "ais/ais_context.h"
#include "ais/ais_curve.h"
#include "ais/ais_frame.h"
#include "ais/ais_light.h"
#include "ais/ais_vector.h"
#include "cmd/command.h"
#include "geom/geom_curve_discrete.h"
#include "kernel/transform.h"
#include "math/nonlinear_math.h"

#define PI 3.1415926535
#define RAD(a) (a) / 180.0 * PI
#define ANGLE(a) (a) / PI * 180.0

CAGD::AIS_Context::AIS_Context(MainOpenGLWidget *parent) : m_parent(parent)
{
    // 初始化
    m_info.depth[0] = m_info.depth[1] = 2;
    m_info.points[0] = m_info.points[1] = m_info.points[2] = {0, 0};
    m_info.size = {0, 0};
    m_info.mouseStatus = MouseStatus::NONE;
    m_info.viewMode = ViewMode::OBSERVE;
    m_info.shadeMode = ShadeMode::FILL;
    m_info.cameraMode = CameraMode::ROTATE;
    m_info.zebra = 0;
    m_info.linewidth = 2;
    m_info.skyboxTex = 0;
    m_info.originPos = {0, 0, 0};
    m_info.viewPos = {100, 100, 100};

    // 刷新相机
    UpdateCamera();
}

CAGD::AIS_Context &CAGD::AIS_Context::ShareContext(MainOpenGLWidget *parent)
{
    static AIS_Context context(parent);
    return context;
}

void CAGD::AIS_Context::InitContext()
{
    // 创建辅助网格线
    for (int i = 0; i <= 20; i++)
    {
        double x = -100.0 + i * 10.0;

        std::vector<BPnt> p1;
        p1.push_back({100.0, x, 0.0});
        p1.push_back({-100.0, x, 0.0});
        Handle(GeomDiscreteCurve) curve1(new GeomDiscreteCurve(p1));
        Handle(AIS_Curve) c1(new AIS_Curve(curve1, true));
        c1->SetActive(false);
        this->CreateObject(c1);

        std::vector<BPnt> p2;
        p2.push_back({x, 100.0, 0.0});
        p2.push_back({x, -100.0, 0.0});
        Handle(GeomDiscreteCurve) curve2(new GeomDiscreteCurve(p2));
        Handle(AIS_Curve) c2(new AIS_Curve(curve2, true));
        c2->SetActive(false);
        this->CreateObject(c2);
    }

    // 创建坐标轴，不发生交互
    Handle(GeomPoint) O(new GeomPoint({0, 0, 0}));
    Handle(GeomVector) x0(new GeomVector({10, 0, 0}));
    Handle(AIS_Vector) x(new AIS_Vector(x0, O));
    Handle(GeomVector) y0(new GeomVector({0, 10, 0}));
    Handle(AIS_Vector) y(new AIS_Vector(y0, O));
    Handle(GeomVector) z0(new GeomVector({0, 0, 10}));
    Handle(AIS_Vector) z(new AIS_Vector(z0, O));
    x->SetActive(false);
    y->SetActive(false);
    z->SetActive(false);
    this->CreateObject(x);
    this->CreateObject(y);
    this->CreateObject(z);

    // 创建初始光照
    Light l;
    l.position = {0, 0, 100};
    l.direction = -l.position * 0.1;
    l.ambient = {0.5f, 0.5f, 0.5f};
    l.diffuse = {0.4f, 0.4f, 0.4f};
    l.specular = {0.8f, 0.8f, 0.8f};
    auto light = Handle(AIS_DirLight)(new AIS_DirLight(l));
    this->CreateObject(light);

    // 移除死亡物体
    ClearDeadObjects();
}

void CAGD::AIS_Context::DeleteContext()
{
    m_objects.clear();
    std::stack<Handle(Command)>().swap(m_cmds);
    m_selectObj = nullptr;
    m_selectedObjs.clear();
}

void CAGD::AIS_Context::MousePressEvent(Point pos, Button button)
{
    if (button == Button::LEFT)
    {
        m_info.points[0] = pos;

        // 碰撞检测
        ClickTest(m_info.points[0]);

        // 指令捕获信息
        if (m_cmds.size() > 0)
        {
            // 这里要刷新窗口显示，提示命令

            // 如果异常就取消命令
            auto cs = m_cmds.top()->Step('l');
            if (cs == CmdStatus::TERMINATE)
                m_cmds.pop();

            // 如果做了什么，就刷新所有物体的渲染数据
            if (cs != CmdStatus::KEEP)
            {
                for (auto obj : m_objects)
                    obj->UpdateArray();
            }
        }
    }
    else if (button == Button::RIGHT)
    {
        m_info.mouseStatus = MouseStatus::MOVE;
        m_info.cameraMode = CameraMode::TRANSLATE;
    }
    else if (button == Button::MIDDLE)
    {
        m_info.mouseStatus = MouseStatus::MOVE;

        // 向所含操作轴发送消息，目的是让向量更新长度
        // 向下传递，目的是让单独的 vector 不接收这个消息
        for (auto obj : m_objects)
            obj->PassMessageDown(Message::SCALE);
    }
}

void CAGD::AIS_Context::MouseReleaseEvent(Point pos)
{
    // 只有当处于移动状态时才解除状态
    if (m_info.mouseStatus == MouseStatus::MOVE)
    {
        m_info.mouseStatus = MouseStatus::NONE;
        m_info.cameraMode = CameraMode::ROTATE;
    }
    else
    {
        // 释放被选中的物体
        if (m_selectObj != nullptr)
        {
            // 恢复旋转模式
            m_info.cameraMode = CameraMode::ROTATE;

            m_selectObj->Release(pos);
            m_selectObj = nullptr;
        }
    }
}

void CAGD::AIS_Context::MouseMoveEvent(Point pos)
{
    if (m_info.mouseStatus == MouseStatus::MOVE)
        UpdateCamera({pos.x - m_info.points[1].x, pos.y - m_info.points[1].y}, {0, 0});
    else
    {
        // 指令捕获信息
        if (m_cmds.size() > 0)
        {
            // 这里要刷新窗口显示，提示命令

            // 如果异常就取消命令
            auto cs = m_cmds.top()->Move();
            if (cs == CmdStatus::TERMINATE)
                m_cmds.pop();

            // 如果做了什么，就刷新所有物体的渲染数据
            if (cs != CmdStatus::KEEP)
            {
                for (auto obj : m_objects)
                    obj->UpdateArray();
            }
        }

        // 如果当前正在选中物体，就移动物体
        if (m_selectObj != nullptr)
            m_selectObj->Move(pos);

        // 移动检测
        MoveTest(pos);
    }

    // 向所含操作轴发送消息，目的是让向量更新长度
    for (auto obj : m_objects)
        obj->PassMessageDown(Message::SCALE);

    // 总是更新屏幕坐标
    m_info.points[1] = m_info.points[2] = pos;
}

void CAGD::AIS_Context::MouseWheelEvent(Point angleDelta)
{
    // 调整相机
    UpdateCamera({0, 0}, angleDelta);

    // 向所含操作轴发送消息，目的是让向量更新长度
    for (auto obj : m_objects)
        obj->PassMessageDown(Message::SCALE);
}

void CAGD::AIS_Context::KeyPressEvent(Key key)
{
    // 记录按键
    m_info.key = key;

    switch (key)
    {
    case Key::SPACE: {
        // 按空格执行指令（回车捕捉不到）
        if (m_cmds.size() > 0)
        {
            // 这里要刷新窗口显示

            // 执行命令
            m_cmds.top()->Perform();

            // 刷新所有物体的渲染数据
            for (auto obj : m_objects)
                obj->UpdateArray();

            m_cmds.pop();
        }

        // 取消所有状态
        m_info.mouseStatus = MouseStatus::NONE;

        break;
    }
    case Key::ESC: {
        // 中断命令
        m_cmds.pop();
        break;
    }
    case Key::S: {
        // 切换渲染模式
        if (m_info.shadeMode == ShadeMode::FILL)
            m_info.shadeMode = ShadeMode::LINE;
        else
            m_info.shadeMode = ShadeMode::FILL;
        break;
    }
    case Key::V: {
        // 切换模式
        if (m_info.viewMode == ViewMode::INFO)
        {
            m_info.viewMode = ViewMode::OBSERVE;

            // 隐藏信息
            for (auto obj : m_objects)
                obj->RecvMessage(Message::HIDE_INFO);
        }
        else if (m_info.viewMode == ViewMode::OBSERVE)
        {
            m_info.viewMode = ViewMode::CONTROL;

            // 显示控制点
            for (auto obj : m_objects)
                obj->RecvMessage(Message::SHOW_CTRL);
        }
        else if (m_info.viewMode == ViewMode::CONTROL)
        {
            m_info.viewMode = ViewMode::INFO;

            // 隐藏控制点，显示信息
            for (auto obj : m_objects)
            {
                obj->RecvMessage(Message::HIDE_CTRL);
                obj->RecvMessage(Message::SHOW_INFO);
            }
        }

        break;
    }
    case Key::X: {
        // 移除选中的物体
        for (auto obj : m_selectedObjs)
            obj->RecvMessage(Message::DEAD);

        m_selectedObjs.clear();
        m_selectObj = nullptr;
        m_info.mouseStatus = MouseStatus::NONE;

        break;
    }
    case Key::Z: {
        // 启动斑马纹
        m_info.zebra = m_info.zebra == 10 ? 0 : 10;
        break;
    }
    case Key::K0: {
        // 回归原点
        if (m_info.mouseStatus == MouseStatus::NONE)
        {
            m_info.originPos = {0, 0, 0};
            UpdateCamera();
        }
        break;
    }
    }

    // 移除死亡物体
    ClearDeadObjects();
}

void CAGD::AIS_Context::KeyReleaseEvent(Key key)
{
    // 释放所有按键
    m_info.key = Key::NONE;
}

void CAGD::AIS_Context::PaintEvent()
{
    static int count = 0;
    static clock_t time = clock();
    static std::vector<std::string> info({"FPs:0"});

    ////////////////////////////////////////////////////////////////////////////////

    // 注意使用普通指针而不是智能指针，防止其生命周期过长导致在 parent 之后析构
    static AIS_Frame *viewFrame = new AIS_Frame({10, 10}, {400, 100});

    // 初始化 frame
    if (viewFrame->GetTexts().size() == 0)
        this->CreateObject(Handle(AIS_Frame)(viewFrame));

    // 计时
    clock_t t = clock();
    auto &text = viewFrame->GetTexts();
    text.clear();

    auto &pos = m_info.viewPos;
    std::string x = std::to_string(pos.X());
    std::string y = std::to_string(pos.Y());
    std::string z = std::to_string(pos.Z());
    text.push_back("Camera:(" + x + "," + y + "," + z + ")");
    text.push_back("File Path:xxx");
    text.push_back("Object Name:xxx");
    text.push_back("Type Name:xxx");
    text.push_back("Vertex Number:0");

    // 记录视图信息
    std::string fps = std::to_string(count);
    double dt = double(t - time) / CLOCKS_PER_SEC;
    if (dt >= 1)
    {
        text.push_back("FPs:" + fps);

        time = clock();
        count = 0;
    }
    else
        text.push_back(info.back());

    // 刷新记录
    info = text;
    count++;

    ////////////////////////////////////////////////////////////////////////////////

    // 绘制 2d 参数域
    static AIS_Frame *paraFrame = new AIS_Frame({790, 10}, {200, 200});

    // 初始化 frame
    if (paraFrame->GetTexts().size() == 0)
        this->CreateObject(Handle(AIS_Frame)(paraFrame));

    paraFrame->GetTexts() = {"Para Field:"};

    ////////////////////////////////////////////////////////////////////////////////

    // 绘制所有物体
    for (auto obj : m_objects)
        obj->Draw();
}

void CAGD::AIS_Context::ResizeEvent(int width, int height)
{
    // 更新尺寸
    m_info.size.x = width;
    m_info.size.y = height;

    // 修改投影矩阵
    m_info.projectionMat = Transform::Perspective(45.0f, 1.0 * width / height, 0.1f, 1000.0f);

    // 向所含操作轴发送消息，目的是让向量更新长度
    for (auto obj : m_objects)
        obj->PassMessageDown(Message::SCALE);
}

void CAGD::AIS_Context::CreateCommand(std::string cmd)
{
    // 通过工厂构建命令
    CommandFactory &factory = CommandFactory::CreateFactory();
    m_cmds.push(factory.CreateProduct(cmd));
}

void CAGD::AIS_Context::CreateObject(Handle(AIS_Base) object)
{
    // 注册渲染器，然后记录物体
    object->RegisterRenderer(m_parent);
    m_objects.push_back(object);
}

CAGD::BPntf CAGD::AIS_Context::GetSpacePoint(Point pos, float z) const
{
    auto &view = m_info.viewMat;
    auto &projection = m_info.projectionMat;

    // 计算屏幕点对应的空间点
    float X = pos.x * 2.0 / m_info.size.x - 1;
    float Y = -pos.y * 2.0 / m_info.size.y + 1;

    // z 坐标越接近 1，得到的点越远离相机，反之越接近相机
    z = z > 1 ? 1.0 - 0.4 / m_info.viewPos.Distance(m_info.originPos) : z;
    BPnt4df v = {X, Y, z, 1};
    v = Transform::Inverted(view) * Transform::Inverted(projection) * v;
    v = v / v.W();

    return {v.X(), v.Y(), v.Z()};
}

CAGD::BPnt2df CAGD::AIS_Context::GetScreenPoint(BPntf pos, float &depth) const
{
    auto &view = m_info.viewMat;
    auto &projection = m_info.projectionMat;

    BPnt4df p = {pos.X(), pos.Y(), pos.Z(), 1};
    BPnt4df q = projection * view * p;

    // 这个时候非常接近屏幕，将深度设为 2
    float X = 0, Y = 0;
    if (fabs(q.W()) < CAGD_ABS_ACC)
        depth = 2;
    else
    {
        q = q / q.W();

        X = (q.X() + 1) * m_info.size.x / 2;
        Y = -(q.Y() - 1) * m_info.size.y / 2;

        depth = q.Z();
    }

    return {X, Y};
}

float CAGD::AIS_Context::GetDepth() const
{
    return (m_info.depth[0] + m_info.depth[1]) / 2;
}

void CAGD::AIS_Context::SetDepth(float depth)
{
    // 记录两个深度，标记最浅 0 和次浅 1 的深度
    if (m_info.depth[0] > depth)
    {
        m_info.depth[1] = m_info.depth[0];
        m_info.depth[0] = depth;
    }
    else if (m_info.depth[1] > depth)
        m_info.depth[1] = depth;
}

Handle(CAGD::AIS_Base) CAGD::AIS_Context::GetSelected() const
{
    if (m_selectedObjs.size() == 0)
        return nullptr;
    return m_selectedObjs.back();
}

std::vector<Handle(CAGD::AIS_Base)> &CAGD::AIS_Context::GetObjects()
{
    return m_objects;
}

CAGD::ContextInfo &CAGD::AIS_Context::GetInfo()
{
    return m_info;
}

void CAGD::AIS_Context::UpdateCamera()
{
    // 获得相机信息
    float distance = m_info.viewPos.Distance(m_info.originPos);

    // 计算欧拉角
    BVecf front = (m_info.originPos - m_info.viewPos).Normalized();
    float pitch = acos(front.Z());
    float yaw = acos(front.X() / sin(pitch));
    if (front.Y() < 0)
        yaw = PI * 2 - yaw;

    // 然后叉乘计算另外两个方向
    BVecf right = (front % BVecf{0, 0, 1}).Normalized();
    BVecf up = (right % front).Normalized();

    // 计算视图矩阵
    m_info.viewPos = m_info.originPos - front * distance;
    m_info.viewMat = Transform::LookAt(m_info.viewPos, m_info.originPos, up);
}

void CAGD::AIS_Context::UpdateCamera(Point dxy, Point delta)
{
    // 获得相机信息
    float distance = m_info.viewPos.Distance(m_info.originPos);

    // 计算欧拉角
    BVecf front = (m_info.originPos - m_info.viewPos).Normalized();
    float pitch = acos(front.Z());
    float yaw = acos(front.X() / sin(pitch));
    if (front.Y() < 0)
        yaw = PI * 2 - yaw;

    switch (m_info.cameraMode)
    {
    case CameraMode::ROTATE: {
        // 屏幕点击移动操作
        float rate = 0.01;

        // 注意限制纵向的角度
        pitch = std::max(0.1f, std::min(pitch + dxy.y * rate, 3.13f));
        yaw -= dxy.x * rate;

        // 设置滚轮操作
        float z1 = 0.8;
        Point pos = m_info.points[1];
        BPnt q1 = GetSpacePoint(pos, z1);
        BPnt q2 = m_info.viewPos;

        // 沿着透视方向缩放
        BVec v = (q2 - q1).Normalized();
        rate = 0.05 * distance;
        if (delta.y > 0)
            rate *= -1;
        if (delta.y == 0)
            rate = 0;
        m_info.originPos += v * rate;

        break;
    }
    case CameraMode::TRANSLATE: {
        // 获得必要点
        float depth;
        BPnt q = m_info.originPos;
        BPnt vp = m_info.viewPos;
        BPnt p = GetSpacePoint({dxy.x + int(m_info.size.x / 2), dxy.y + int(m_info.size.y / 2)});

        // 计算与平面的交点，注意往反方向移动
        BVec n = (vp - p).Normalized();
        BPnt tar = p + n * (n * (q - p));
        m_info.originPos += m_info.originPos - tar;

        break;
    }
    default:
        break;
    }

    // 先算出方向，然后规范化
    front = BVecf(cos(yaw) * sin(pitch), sin(yaw) * sin(pitch), cos(pitch)).Normalized();

    // 然后叉乘计算另外两个方向
    BVecf right = (front % BVecf{0, 0, 1}).Normalized();
    BVecf up = (right % front).Normalized();

    // 计算视图矩阵
    m_info.viewPos = m_info.originPos - front * distance;
    m_info.viewMat = Transform::LookAt(m_info.viewPos, m_info.originPos, up);
}

void CAGD::AIS_Context::ClearDepth()
{
    m_info.depth[0] = 2;
    m_info.depth[1] = 2;
}

void CAGD::AIS_Context::ClearDeadObjects()
{
    // 移除所有死亡物体
    for (auto it = m_objects.begin(); it != m_objects.end();)
    {
        if ((*it)->GetInfo().status == AIS_Status::DEAD)
            it = m_objects.erase(it);
        else
            ++it;
    }
}

void CAGD::AIS_Context::ClearAllObjects()
{
    // 移除物体，需要解决光源问题
    m_objects.clear();

    // 清除选中状态
    m_info.mouseStatus = MouseStatus::NONE;
    m_selectObj = nullptr;
    m_selectedObjs.clear();
}

void CAGD::AIS_Context::ClickTest(Point pos)
{
    // 清除正在选中的物体
    m_selectObj = nullptr;

    // 非 Shift 情况下单选
    if (m_info.key != Key::SHIFT)
        m_selectedObjs.clear();

    // 进行碰撞检测
    for (auto obj : m_objects)
    {
        if (obj->Click(m_info.points[0]))
            m_info.mouseStatus = MouseStatus::TOUCH;
    }

    // 进行深度检测
    for (auto obj : m_objects)
    {
        // 正常只会有一个物体进入 if，设为选中状态，发送点选消息
        if (obj->UpdateDepth())
        {
            m_selectObj = obj;
            m_selectedObjs.push_back(obj);
        }

        // 隐藏操作轴
        obj->RecvMessage(Message::NONE);
    }

    // 选中已选中的物体
    for (auto obj : m_selectedObjs)
        obj->RecvMessage(Message::SELECT);

    // 清除全局深度
    ClearDepth();
}

void CAGD::AIS_Context::MoveTest(Point pos)
{
    // 进行碰撞检测，不检测被选中的物体
    for (auto obj : m_objects)
    {
        if (obj->GetInfo().status != AIS_Status::TOUCH && obj->MoveTo(pos))
            m_info.mouseStatus = MouseStatus::HANG;
    }

    // 进行深度检测
    for (auto obj : m_objects)
        obj->UpdateDepth();

    // 清除全局深度
    ClearDepth();
}
